﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data.SqlClient;
using System.Collections;
using System.Net;
using System.Xml;
using System.Globalization;
using System.Reflection;
using System.Diagnostics;

using LostLib.Programs.Properties;

namespace LostLib.Programs
{
	static class CliAssemblyManager
	{
		static void Search(IEnumerable<string> args)
		{
			string AssemblyName = null;
			string desc = null;

			#region parsing arguments
			var enumerator = args.GetEnumerator();
			while (enumerator.MoveNext())
			{
				switch (enumerator.Current)
				{
				case "-desc":
					if (desc != null) throw new ArgumentException("duplicate -desc");
					desc = enumerator.GetNext();
					break;
				case "-name":
					if (AssemblyName != null) throw new ArgumentException("duplicate -name");
					AssemblyName = enumerator.GetNext();
					break;
				default:
					if (AssemblyName != null) throw new ArgumentException("duplicate -name");
					AssemblyName = enumerator.Current;
					break;
				}
			}
			#endregion

			#region searching
			var AssemblyInfos =
				AssemblyManager.ApplicationDatabase.AssemblyInfos
					.Where(Assembly =>
						((AssemblyName == null) || Assembly.AssemblyName.Contains(AssemblyName))
						&& ((desc == null) || Assembly.Description.Contains(desc)));

			foreach (var Assembly in AssemblyInfos)
			{
				var latest = Assembly.LatestSuitable;
				if (latest == null) continue;
				var installed = Assembly.InstalledVersion;

				if (installed == null) Console.Write("not installed ");
				else if (installed.Version == latest.Version) Console.Write("latest ");
				else Console.Write("old ");

				Console.WriteLine("@{0}", latest.SourceID);
				if (installed != null)
					Console.Write("{0} ", installed);
				Console.WriteLine(latest);
				Console.WriteLine(Assembly.Description);
				Console.WriteLine();
			}
			#endregion
		}

		#region DEBUG
		[Conditional("DEBUG")]
		internal static void WriteDebug(string str)
		{
			Console.Write(str);
		}
		[Conditional("DEBUG")]
		internal static void WriteDebug(string fmt, object arg0)
		{
			Console.Write(fmt, arg0);
		}
		[Conditional("DEBUG")]
		internal static void WriteDebug(string fmt, object arg0, object arg1)
		{
			Console.Write(fmt, arg0, arg1);
		}
		[Conditional("DEBUG")]
		internal static void WriteDebug(string fmt, object arg0, object arg1, object arg2)
		{
			Console.Write(fmt, arg0, arg1, arg2);
		}
		#endregion DEBUG

		static int HandleCommand(string[] args)
		{
			switch (args[0])
			{
			case "silent":
				AssemblyManager.InstallApprover -= cliInstallApprover;
				AssemblyManager.RemoveApprover -= cliRemoveApprover;
				AssemblyManager.InstallApprover += silentApprover;
				AssemblyManager.RemoveApprover += silentApprover;
				return HandleCommand(args.Skip(1).ToArray());
			case "sources-list":
				SourceList(args.Skip(1));
				break;
			case "help":
				Console.WriteLine("Available commands: ");
				Console.WriteLine("  sources-list - lists all sources");
				Console.WriteLine("  sources-add NAME1 PATH1 [NAME2 PATH2 ...] - adds a source");
				Console.WriteLine("  sources-remove NAME1 [NAME2 ...] - removes specified source");
				Console.WriteLine("  sources-update [NAME1 ...] - updates Assembly list from specified source (or from all sources)");
				Console.WriteLine("  search [-desc] Assembly - searches database for Assembly name or description");
				Console.WriteLine("  install [Assembly1 ...] - installs specified AssemblyInfos");
				Console.WriteLine("  remove [Assembly1 ...] - removes specified AssemblyInfos");
				Console.WriteLine("  config-reconfigure [install|remove] [built-in] - installs/removes db or/and rebuild suitable list");
				break;
			case "sources-add":
				SourceAdd(args.Skip(1));
				break;
			case "gac-list":
				GacList(args.Skip(1));
				break;
			case "sources-remove":
				SourceRemove(args.Skip(1));
				break;
			case "sources-update":
				SourceUpdate(args.Skip(1));
				break;
			case "search":
				Search(args.Skip(1));
				break;
			case "install":
				Install(args.Skip(1));
				break;
			case "remove":
				Remove(args.Skip(1));
				break;
			case "config-reconfigure":
				return ConfigReconfigure(args.Skip(1));
			case "config-show":
				ConfigShow(args.Skip(1));
				break;
			case "":
				if (!AssemblyManager.Installed)
				{
					ConfigReconfigure(new string[] { "database" });
					return 0;
				}
				break;
			default:
				Console.Error.WriteLine("invalid command {0}", args[0]);
				break;
			}
			return 0;
		}

		private static void GacList(IEnumerable<string> parameters)
		{
			foreach (var assembyName in GlobalAssemblyCache.GetAssemblies(false))
			{
				Console.WriteLine(assembyName);
			}
		}

		static EventHandler<ApproveEventArgs<IEnumerable<AssemblyVersion>>> cliInstallApprover =
			new EventHandler<ApproveEventArgs<IEnumerable<AssemblyVersion>>>(AssemblyManager_InstallApprover);
		static EventHandler<ApproveEventArgs<IEnumerable<AssemblyVersion>>> cliRemoveApprover =
			new EventHandler<ApproveEventArgs<IEnumerable<AssemblyVersion>>>(AssemblyManager_RemoveApprover);
		static EventHandler<ApproveEventArgs<IEnumerable<AssemblyVersion>>> silentApprover =
			new EventHandler<ApproveEventArgs<IEnumerable<AssemblyVersion>>>(AssemblyManager_SilentApprover);

		static int Main(
				string[] args
			)
		{

			Console.WriteLine("LostLib.Programs v{0}", Assembly.GetExecutingAssembly().GetName().Version);
			if (!AssemblyManager.Installed)
			{
				ConfigReconfigure(new string[] { "install" });
				if (!AssemblyManager.Installed) return 0;
			}

			AssemblyManager.InstallApprover += cliInstallApprover;
			AssemblyManager.RemoveApprover += cliRemoveApprover;

			Console.Write("accessing database...");
			foreach (var item in AssemblyManager.ApplicationDatabase.AssemblySources)
				;
			Console.WriteLine("OK");
			if (args.Length == 0)
			{
				Console.Write("#");
				for (string command = Console.ReadLine(); (command != null) && AssemblyManager.Installed; Console.Write("#"), command = Console.ReadLine())
				{
					args = command.Split(' ');
					if (HandleCommand(args) == 1) return 1;
				}
			} else HandleCommand(args);
			return 0;
		}

		static void AssemblyManager_RemoveApprover(object sender, ApproveEventArgs<IEnumerable<AssemblyVersion>> args)
		{
			if (args.Subject.Count() == 0)
			{
				Console.WriteLine("no AssemblyInfos matched");
				args.Approved = true;
			} else
			{
				Console.WriteLine("do you want to remove the following assemblies:");
				Console.Write("    ");
				foreach (var item in args.Subject)
					Console.Write("{0} ", item);
				Console.WriteLine();
				args.Approved = String.IsNullOrEmpty(Console.ReadLine());
			}
		}

		static void AssemblyManager_SilentApprover(object sender, ApproveEventArgs<IEnumerable<AssemblyVersion>> args)
		{
			args.Approved = true;
		}

		static void AssemblyManager_InstallApprover(object sender, ApproveEventArgs<IEnumerable<AssemblyVersion>> args)
		{
			if (args.Subject.Count() == 0)
			{
				Console.WriteLine("no assemblies matched");
				args.Approved = true;
			} else
			{
				Console.WriteLine("do you want to install the following assemblies:");
				Console.Write("    ");
				foreach (var item in args.Subject)
					Console.Write("{0} ", item);
				Console.WriteLine();
				args.Approved = String.IsNullOrEmpty(Console.ReadLine());
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "args")]
		private static void ConfigShow(IEnumerable<string> args)
		{
			Console.WriteLine("database configuration");
			Console.WriteLine("connection string: {0}", Settings.Default.ProgramsDB);
			Console.WriteLine();


			Console.WriteLine("cache configuration");
			Console.WriteLine("cache path: {0}", Settings.Default.AssemblyCache);
			Console.WriteLine();
		}

		private static int ConfigReconfigure(IEnumerable<string> args)
		{
			foreach (var configSection in args)
			{
				switch (configSection)
				{
				case "install":
					Console.Write("install LostLib.Programs?");
					if (Console.ReadKey().Key != ConsoleKey.Enter)
						break;
					Console.WriteLine();

					Directory.CreateDirectory(Settings.Default.AssemblyCache);
					AssemblyManager.CreateDatabase();

					Console.Write("do you want to add demo source?");
					if (Console.ReadKey().Key == ConsoleKey.Enter)
						AssemblyManager.AddSource("main", "http://lostmsu.broker.freenet6.net/CliPack/AssemblyDB.xml");
					Console.WriteLine();

					Console.Write("update sources now?");
					if (Console.ReadKey().Key == ConsoleKey.Enter)
						SourceUpdate(new string[] { });
					Console.WriteLine();
					break;
				case "remove":
					AssemblyManager.ApplicationDatabase.DeleteDatabase();
					return 1;
				default:
					throw new NotSupportedException(configSection);
				}
				Settings.Default.Save();
			}
			Settings.Default.Save();
			return 0;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static void Install(IEnumerable<string> args)
		{
#if !DEBUG
			try
			{
#endif
				AssemblyManager.Install(args.Select(arg => AssemblyVersionFilter.Parse(arg, CultureInfo.CurrentCulture)));
#if !DEBUG
			} catch (Exception cancel)
			{
				Console.WriteLine("ERROR");
				Console.WriteLine("{0}: {1}", cancel.GetType().Name, cancel.Message);
			}
#endif
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static void Remove(IEnumerable<string> args)
		{
#if !DEBUG
			try
			{
#endif
				AssemblyManager.Remove(args
					.Select(arg => AssemblyVersionFilter.Parse(arg, CultureInfo.CurrentCulture)));
#if !DEBUG
			} catch (Exception cancel)
			{
				Console.WriteLine("ERROR");
				Console.WriteLine("{0}: {1}", cancel.GetType().Name, cancel.Message);
			}
#endif
		}

		private static void SourceUpdate(IEnumerable<string> args)
		{
			var count = 0;
			foreach (var sourceName in args)
			{
				AssemblyManager.UpdateSource(sourceName);
				count ++;
			}

			if(count == 0)
				foreach (var sourceName in AssemblyManager.ApplicationDatabase.AssemblySources.Select(source => source.Name))
					AssemblyManager.UpdateSource(sourceName);

			AssemblyManager.RebuildSuitableVersionList();
			AssemblyManager.ApplicationDatabase.SubmitChanges();
		}

		private static void SourceRemove(IEnumerable<string> args)
		{
			foreach (var arg in args)
				AssemblyManager.RemoveSource(arg);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "args")]
		private static void SourceList(IEnumerable<string> args)
		{
			foreach (var source in AssemblyManager.ApplicationDatabase.AssemblySources)
				Console.WriteLine(source);
		}
		private static void SourceAdd(IEnumerable<string> args)
		{
			var iterator = args.GetEnumerator();
   			var name = iterator.GetNext();
			var uri = iterator.GetNext();

			AssemblyManager.AddSource(name, uri);
		}
	}
}
